import itertools
from math import sin as _sin, sqrt as _sqrt
from random import uniform as _uniform, randint as _randint
from numpy import ndarray as _ndarray, asarray

__all__ = [
    # "fade1",
    "fade2",
    # "UnitInterpolation",
    # "UnitInterpolation2",
    "Unitization",
    "FYShuffle",
    "Noise2D",
    "wood",
    "marble",
    "text",
]


# def fade1(x):
#     return (3-x*2)*x*x


def fade2(x):
    return (x * (x * 6 - 15) + 10) * x * x * x


# def UnitInterpolation(x, value, func=lambda x: x):
#     """一维线性插值"""
#     return value[0]+func(x)*(value[1]-value[0])


def UnitInterpolation2(x: tuple, value: tuple, func=lambda x: x) -> float:
    """二维线性插值"""
    x0 = value[0][0] + func(x[1]) * (value[0][1] - value[0][0])
    x1 = value[1][0] + func(x[1]) * (value[1][1] - value[1][0])
    return x0 + func(x[0]) * (x1 - x0)


def Unitization(*l) -> tuple:
    """单位化"""
    length = 1 / _sqrt(sum(i * i for i in l))
    return tuple(i * length for i in l)


def Unitization_2D(x: float, y: float) -> tuple:
    """单位化"""
    leng = 1 / _sqrt(x ** 2 + y ** 2)
    return x * leng, y * leng


def FYShuffle(num: int) -> tuple:
    """生成Fisher-Yates洗牌算法的置换表"""
    o = list(range(num))
    for i in reversed(range(num)):
        j = _randint(0, i)
        o[i], o[j] = o[j], o[i]
    return tuple(o)


class Noise2D:
    """二维柏林噪声生成器"""

    def __init__(self, size: int):
        self.size: int = size
        self.gradients: tuple[tuple[float]] = tuple(
            Unitization_2D(_uniform(-1, 1), _uniform(-1, 1)) for _ in range(size)
        )
        self.permutations: tuple[int] = FYShuffle(size)

    def getIndex(self, x: int, y: int) -> int:
        """得到对应坐标的梯度"""
        size = self.size
        permutations = self.permutations
        return permutations[(permutations[y % size] + x) % size]

    def perlinNoise(self, x: float, y: float) -> float:
        qx0, qy0 = int(x), int(y)
        qx1, qy1 = qx0 + 1, qy0 + 1
        lx0, ly0 = x - qx0, y - qy0
        lx1, ly1 = lx0 - 1, ly0 - 1
        lx3, ly3 = fade2(lx0), fade2(ly0)
        getIndex, gradients = self.getIndex, self.gradients
        q000, q001 = gradients[getIndex(qx0, qy0)]
        q100, q101 = gradients[getIndex(qx1, qy0)]
        q010, q011 = gradients[getIndex(qx0, qy1)]
        q110, q111 = gradients[getIndex(qx1, qy1)]
        v00 = q000 * lx0 + q001 * ly0
        v10 = q100 * lx1 + q101 * ly0
        v01 = q010 * lx0 + q011 * ly1
        v11 = q110 * lx1 + q111 * ly1
        x0 = v00 + ly3 * (v01 - v00)
        x1 = v10 + ly3 * (v11 - v10)
        # mx = asarray(((q000, q010),
        #               (q100, q110)))
        # mx = (-ly1, ly0)@((lx0*(1-lx3), lx1*lx3)@mx)
        # my = asarray(((q001, q101),
        #               (q011, q111)))
        # # my = (-lx1, lx0)@((ly0*(1-ly3), ly1*ly3)@my)
        return (x0 + lx3 * (x1 - x0)) * 0.7 + 0.5

    def FBM(self, x: float, y: float, step: int = 1) -> float:
        """分形布朗运动 - Fractal Brown Motion"""
        if step == 1:
            return self.perlinNoise(x, y)
        value = 0.0
        amplitude = 1 / (2 - 0.5 ** (step - 1))
        frequency = 1
        perlinNoise = self.perlinNoise
        for _ in range(step):
            value += amplitude * perlinNoise(x * frequency, y * frequency)
            frequency *= 2
            amplitude *= 0.5
        return value

    def genImage_perlinNoise(self, channel: int, scale: float) -> _ndarray:
        assert channel > 0
        size = self.size
        scale /= size
        img = _ndarray((size, size, channel), dtype="float64")
        perlinNoise = self.perlinNoise
        for j in range(size):
            for i, k in itertools.product(range(size), range(channel)):
                img[i][j][k] = perlinNoise((i + k * size) * scale, j * scale)
        return img


def wood(x, y, a):
    return a * 10 - int(a * 10)

def marble(x, *_):
    return _sin(x) * 0.5 + 0.5


def text(x, y, a):
    return _sin(_sin(x) + _sin(y) + 10 * _sin(a)) * 0.5 + 0.5

